package table

import (
	"container/list"
	"encoding/json"
	"fmt"
	"github.com/name5566/leaf/gate"
	nest_cache "lol.com/feb214-game/nest.git/cache"
	"lol.com/feb214-game/nest.git/cricket"
	"lol.com/feb214-game/nest.git/leaf"
	"lol.com/feb214-game/nest.git/log"
	"lol.com/feb214-game/nest.git/proto"
	"lol.com/feb214-game/nest.git/tools/tz"
	"strconv"
	"sync"
	"sync/atomic"
	"talent.com/cube/common/cache"
	"talent.com/cube/common/cxt"
	"talent.com/cube/common/msg"
	"talent.com/cube/conf"
	"talent.com/cube/conf/g"
	Jmg "talent.com/cube/modules/game/internal/table/jmg"
	"time"
)

func DispatchUser(uc *cxt.UserContext) bool {
	var (
		tc *cxt.TableContext
		//cas []*cxt.TableContext
	)
	//g.AllTables.Range(func(key, value interface{}) bool {
	//	tc = value.(*cxt.TableContext)
	//	if tc.GameType != uc.GameType || tc.Kind != uc.RoomKind {
	//		return true
	//	}
	//	cas = append(cas, tc)
	//	return true
	//})
	////人数越多优先级越高
	//sort.Slice(cas, func(i, j int) bool {
	//	return atomic.LoadInt32(&cas[i].UserCount) > atomic.LoadInt32(&cas[j].UserCount)
	//})
	//var after int32
	//// 尝试将用户添加到现有的房间
	//for _, tc = range cas {
	//	after = atomic.AddInt32(&tc.UserCount, 1)
	//	if after <= g.TableMaxUserCount {
	//		_ = tc.RPC.Call0(g.EventAddUser, uc, tc)
	//		if uc.TableID != 0 {
	//			log.Info("user %v join game %v table %v", uc.ID, g.GetCodeLabel(uc.GameType), tc.ID)
	//			break
	//		}
	//	}
	//	atomic.AddInt32(&tc.UserCount, -1)
	//}
	// 没有房间
	if uc.TableID == 0 {
		// 创建一张预热一定局数的房间
		// 这里判断房间数量，拿出对应的index
		tc = createPreHeatTable(uc.RoomKind, uc.GameType)
		if tc == nil {
			return false
		}
		g.AllTables.Store(tc.ID, tc)
		uc.TableID = tc.ID
		tc.Players.SetPlayers(uc.ID, uc)
		tc.UserCount++
		tc.RealUserId = uc.ID
		// 开始游戏（游戏入口）
		startNewTerm(tc)
		log.Info("user %v join game %v room %v at new table %v",
			uc.ID, g.GetCodeLabel(uc.GameType), uc.RoomKind, uc.TableID)
	} else {
		for _, v := range tc.RichUsers {
			if v.ID == uc.ID {
				v.Credit = uc.Credit
			}
		}
	}
	return true
}

//新建一个空白桌子
func createNewTable(roomKind, gameType int32) *cxt.TableContext {
	tableID, err := cache.IncrTableNo()
	if err != nil {
		log.Error("can't generate table id!!!")
		return nil
	}
	tc := &cxt.TableContext{
		RoomST: &msg.RoomST{
			ID:         tableID,
			Kind:       roomKind,
			BankerList: []*msg.UserST{genSysBanker(gameType)}, //第一局的庄家肯定是系统
		},
		GameType:       gameType,
		Skeleton:       leaf.NewSkeleton(), // 桌子骨架
		Players:        &cxt.SyncPlayersMap{Map: &sync.Map{}},
		CloseChan:      make(chan bool),
		HistoryWinners: list.New(),
		HistoryBetters: list.New(),
		UserBetBuffer:  make(map[uint64]*msg.BetInfo),
		LifeStats: &cxt.LifeStats{
			Counter:      0,
			LastActiveAt: tz.GetNowTs(),
		},
	}
	tc.Chips = make([]int64, len(conf.RoomConfig(gameType, roomKind).Chips))
	copy(tc.Chips, conf.RoomConfig(gameType, roomKind).Chips)

	tc.RPC = tc.Skeleton.ChanRPCServer // 桌子骨架的RPC服务，提供桌内消息的入口
	switch gameType {
	case g.Jmg:
		tc.WorldImpl = Jmg.NewTable(tc)
	}
	registerTableEvent(tc)
	go tc.Skeleton.Run(tc.CloseChan)
	g.ServerCloseWG.Add(1)
	return tc
}

func syncBankerList(tc *cxt.TableContext) {
	broadcast(tc, &msg.BankerST{
		Timestamp: tz.GetNowTsMs(),
		Banker:    tc.BankerList,
	})
}

func broadcast(tc *cxt.TableContext, message interface{}) {
	var ag gate.Agent
	tc.Players.Map.Range(func(key, value interface{}) bool {
		uc := value.(*cxt.UserContext)
		if uc.IsRobot() {
			return true
		}
		if ag = cxt.GetUserAgent(uc.ID); ag != nil {
			ag.WriteMsg(message)
		}
		return true
	})
}

func pushUser(userID uint64, message interface{}) {
	if ag := cxt.GetUserAgent(userID); ag != nil {
		ag.WriteMsg(message)
	}
}

func syncUserST(uc *cxt.UserContext, tc *cxt.TableContext) {
	pushUser(uc.ID, uc.UserST)
}

// 用户进入房间或重连时同步房间状态
func syncRoomST(uc *cxt.UserContext, tc *cxt.TableContext) {
	roomST := *tc.RoomST
	roomST.Timestamp = tz.GetNowTsMs()
	roomST.Me = tc.Players.GetPlayer(uc.ID).UserST
	roomST.TopInfo = tc.RoomST.TopInfo
	pushUser(uc.ID, &roomST)
}

//用户下注，由于缺乏事务性，异常情况下这里可能会造成数据不一致
func userBet(uc *cxt.UserContext, tc *cxt.TableContext, req *msg.BetReq) {
	if !uc.IsRobot() {
		userStats := g.StatsDAO.GetUserStats(uc.ID)
		if userStats.Recharge.Total == 0 && userStats.TransferIn.Amount == 0 {
			pushUser(uc.ID, &proto.ErrorST{
				Timestamp: tz.GetNowTsMs(),
				Status:    proto.STATUS_CUBE_BET_OVER_LIMIT,
				Msg:       "This game is intended for the paid users only.",
			})
			return
		}
		//防止空续压
		if req.Info == nil || len(req.Info) == 0 {
			log.Info("rebet amount is null")
			return
		}
	}
	//增加下注锁 一秒内只能下注一次
	if !uc.IsRobot() && !cache.LockUserBet(uc.ID) {
		log.Warn("user can not bet:%v often", uc.ID)
		return
	}
	if tc.TermNumber != req.TermNumber {
		log.Warn("user %v bet wrong term number, req:%v, now:%v",
			uc.ID, req.TermNumber, tc.TermNumber)
		return
	}
	if tc.Stage != msg.TERM_STAGE_BET {
		log.Info("user %v bet when game %v term %v announcing, ignore",
			uc.ID, tc.GameType, tc.TermNumber)
		return
	}
	if tc.BankerList[0].ID == uc.ID {
		log.Warn("user %v is banker of game %v term %v but bet, ignore", uc.ID, tc.GameType, tc.TermNumber)
		return
	}

	if !uc.IsRobot() {
		//当前下注金额 > 余额  直接返回
		var currTotalBet int64
		for _, chip := range req.Info {
			for amount, count := range chip.Data {
				currTotalBet += amount * int64(count)
			}
		}
		if currTotalBet > uc.Credit {
			log.Warn("bet error! user: %v ,curr user credit: %v, currTotalBet : %v ", uc.ID, uc.Credit, currTotalBet)
			return
		}
	}

	limit := conf.RoomConfig(tc.GameType, tc.Kind).BetLimit
	if !uc.IsRobot() {
		if uc.SyncBet == 0 {
			uc.SyncBet = uc.Credit
			if uc.Credit < limit {
				pushUser(uc.ID, &proto.ErrorST{
					Status: proto.STATUS_INSUFFICIENT,
					Msg:    fmt.Sprintf("%v", limit/g.CoinRate),
				})
				return
			}
		} else if uc.SyncBet < limit {
			pushUser(uc.ID, &proto.ErrorST{
				Status: proto.STATUS_INSUFFICIENT,
				Msg:    fmt.Sprintf("%v", limit/g.CoinRate),
			})
			return
		}
	}

	//第八个区域不允许下注byAiZhi20211201
	if len(uc.BetInfo) == 7 {
		for betIndex := range req.Info {
			if _, ok := uc.BetInfo[betIndex]; !ok {
				//说明有第八个区域
				pushUser(uc.ID, &proto.ErrorST{
					Timestamp: tz.GetNowTsMs(),
					Status:    proto.STATUS_CUBE_BET_OVER_LIMIT,
					Msg:       "Sorry, betting on all options is not allowed.",
				})
				return
			}
		}
	}
	info := make(map[int32]int64, len(req.Info))
	var (
		total  int64
		amount int64
		count  int32
	)
	limit = conf.ServerConfig().Game.IndexBetLimit
	//校验数据
	for index, chip := range req.Info {
		if !tc.WorldImpl.CheckBetIndex(index) {
			log.Warn("user %v bet index %v invalid in game %v", uc.ID, index, g.GetCodeLabel(tc.GameType))
			return
		}
		var current int64
		for amount, count = range chip.Data {
			if count <= 0 {
				return
			}
			//	if roomConf.IsValidChip(amount) {
			if tc.IsValidChip(amount) {
				total += amount * int64(count)
				current += amount * int64(count)
			} else {
				log.Warn("invalid chip:%v from user %v bet request", amount, uc.ID)
				var chips []byte
				//	chips, _ = json.Marshal(roomConf.Chips)
				chips, _ = json.Marshal(tc.Chips)
				pushUser(uc.ID, &proto.ErrorST{
					Timestamp: tz.GetNowTsMs(),
					Status:    proto.STATUS_CUBE_CHIP_ERROR,
					Msg:       string(chips),
				})
				return
			}
		}
		info[index] = current
		chip.Total = current
		if tc.IndexBetCache[index]+current > limit {
			// 真实用户
			if !uc.IsRobot() {
				pushUser(uc.ID, &proto.ErrorST{
					Timestamp: tz.GetNowTsMs(),
					Status:    proto.STATUS_CUBE_BET_OVER_LIMIT,
					Msg:       "The bet in this area has reached the limit.",
				})
			}
			return
		}
	}
	//以用户内存中的钱为准
	if uc.Credit-total < 0 {
		return
	}
	// 真实用户
	if !uc.IsRobot() { //更新蓄水池
		cache.IncrDailyBet(tc.GameType, total)
	}
	for idx, amount := range info {
		uc.Credit -= amount
		if !uc.IsRobot() {
			nest_cache.StreamTrack(g.KafkaPool, g.TopicCommon, strconv.Itoa(int(tc.TermNumber)), cricket.BetTrack{
				Type:       "bet",
				Ts:         time.Now().Unix(),
				UserId:     uc.ID,
				TermNumber: tc.TermNumber,
				BetIndex:   idx,
				Amount:     amount,
				RoomIndex:  tc.Kind,
				TableId:    tc.ID,
				After:      uc.Credit,
				RobotLevel: uc.RobotLevel,
				GameType:   tc.GameType,
				Pkg:        uc.Pkg,
			})
		}
	}
	//记录到内存
	if !uc.IsRobot() {
		if uc.CostSyncCounter.Cleared() {
			uc.CostSyncCounter.Incr()
		}
	}
	tc.CostCache[uc.ID] += total
	atomic.AddInt64(&uc.UnSync, total)
	if tc.UserBetBuffer[uc.ID] == nil {
		tc.UserBetBuffer[uc.ID] = &msg.BetInfo{
			IndexChips: make(map[int32]*msg.ChipInfo),
		}
	}
	betBuffer := tc.UserBetBuffer[uc.ID]
	for betIndex, betInfo := range req.Info {
		// 初始化校验
		if tc.BetInfo[betIndex] == nil {
			tc.BetInfo[betIndex] = &msg.ChipInfo{
				Data: make(map[int64]int32),
			}
		}
		if uc.BetInfo[betIndex] == nil {
			uc.BetInfo[betIndex] = &msg.ChipInfo{
				Data: make(map[int64]int32),
			}
		}
		if betBuffer.IndexChips[betIndex] == nil {
			betBuffer.IndexChips[betIndex] = &msg.ChipInfo{
				Data: make(map[int64]int32),
			}
		}

		// 记录下注数据
		for _, dct := range []*msg.ChipInfo{tc.BetInfo[betIndex], uc.BetInfo[betIndex], betBuffer.IndexChips[betIndex]} {
			for amount, count = range betInfo.Data {
				dct.Data[amount] += count
			}
			dct.Total += betInfo.Total
		}
		tc.IndexBetCache[betIndex] += info[betIndex]
		// 真实用户
		if !uc.IsRobot() {
			tc.RealIndexBetCache[betIndex] += info[betIndex]
		}
	}
	betBuffer.After = uc.Credit
	if req.ReBet {
		betBuffer.ReBet = true
	}
}

func removeBanker(tc *cxt.TableContext, userId uint64) {
	//从庄家队列中移除
	index := -1
	for i, banker := range tc.BankerList {
		if banker.ID == userId {
			index = i
			break
		}
	}
	if index > 0 {
		tc.BankerList = append(tc.BankerList[:index], tc.BankerList[index+1:]...)
		syncBankerList(tc)
	} else if index == 0 {
		tc.Players.GetPlayer(tc.BankerList[0].ID).LeftBankerTimes = -1 //用户主动申请/被动下庄
	}
}

//上下庄
func userBanker(uc *cxt.UserContext, tc *cxt.TableContext, on bool) {
	if !g.IsBankerGame(tc.GameType) {
		return
	}
	// 上庄
	if on {
		bankerLimit := conf.RoomConfig(tc.GameType, tc.Kind).BankerLimit
		if uc.Credit < bankerLimit {
			pushUser(uc.ID, &proto.ErrorST{
				Status: proto.STATUS_CUBE_BANKER_INSUFFICIENT,
				Msg:    fmt.Sprintf("%v not enougth!", bankerLimit/g.CoinRate),
			})
			return
		}
		for _, banker := range tc.BankerList {
			if banker.ID == uc.ID {
				return
			}
		}
		queueSize := len(tc.BankerList)
		if tc.BankerList[0].ID == 0 {
			queueSize--
		}
		if queueSize >= g.MaxBankerQueueSize {
			pushUser(uc.ID, &proto.ErrorST{
				Status: proto.STATUS_CUBE_BANKER_FULL,
			})
			return
		}
		uc.LeftBankerTimes = g.MaxHoldBankerTimers
		tc.BankerList = append(tc.BankerList, uc.UserST)
		syncBankerList(tc)
		return
	}
	// 下庄
	removeBanker(tc, uc.ID)
}
